#pragma once

namespace AT
{

class ICallback
{
public:
	virtual ~ICallback() = default;
	virtual void Invoke() = 0;	// Only call once

	void SetId(UINT nId) { m_nId = nId; }
	UINT GetId() const { return m_nId; }

private:
	UINT m_nId = 0;
};

template<typename Fn, typename... Args>
class CCallbackImpl : public ICallback
{
public:
	template<typename Fn2, typename... Args2>
	CCallbackImpl(Fn2&& fn, Args2&&... args)
		: m_fn(std::forward<Fn2>(fn))
		, m_args(std::forward<Args2>(args)...)
	{
	}

	void Invoke() override
	{
		// Only call once, use std::move to improve performance
		if constexpr (!std::is_same_v<Fn, nullptr_t>)
			std::apply(m_fn, std::move(m_args));
	}

private:
	Fn m_fn;
	std::tuple<Args...> m_args;
};

using ICallbackPtr = std::unique_ptr<ICallback>;

// For performance, don't use std::function and std::bind
template<typename Fn, typename... Args>
inline ICallbackPtr MakeCallback(Fn&& fn, Args&&... args)
{
	// Integral pointers are disabled for async call safety
	static_assert(!(... || (std::is_pointer_v<std::decay_t<Args>> && std::is_integral_v<std::remove_pointer_t<std::decay_t<Args>>>)), "Integral pointer is disabled");

	return std::make_unique<CCallbackImpl<std::decay_t<Fn>, std::decay_t<Args>...>>(std::forward<Fn>(fn), std::forward<Args>(args)...);
}

}	// namespace AT
